﻿namespace PicToGray
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using Util;

    class PicToGrayMain
    {
        static ImageCodecInfo imgCodecInfo;

        /// <summary>
        /// Usage:
        ///  $ bin/Release/PicToGray E:\\input_dir E:\\output_dir X
        /// where X is the number of gray levels.
        /// When X = 0, we transform all images to grayscale images.
        /// When X = 1, we transform all images to black-white images.
        /// When X > 1, we transform all images to images with X gray levels.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            DirectoryInfo src = new DirectoryInfo(args[0]);
            DirectoryInfo dst = new DirectoryInfo(args[1]);
            int level = int.Parse(args[2]);

            imgCodecInfo = ImageProcessor.GetCodecInfo("image/jpeg");

            if (level == 0)
            {
                FileManager.ForEachFileDirToDirTransfer(src, dst, ToGrayImageTransferHandler);
            }
            else if (level == 1)
            {
                FileManager.ForEachFileDirToDirTransfer(src, dst, ToBlackWhiteImageTransferHandler);
            }
            else
            {
                FileManager.ForEachFileDirToDirTransfer(src, dst, ToGrayLevelTransferHandler, level);
            }
        }

        private static void ToGrayImageTransferHandler(FileInfo f, DirectoryInfo dst, params object[] args)
        {
            try
            {
                Image sourceImage = Image.FromFile(f.FullName);
                Bitmap bmp = new Bitmap(sourceImage);

                for (int i = 0; i < bmp.Height; ++i)
                {
                    for (int j = 0; j < bmp.Width; ++j)
                    {
                        Color c = bmp.GetPixel(j, i);
                        int res = (int)(c.R * 0.299 + c.G * 0.587 + c.B * 0.114);  // Convert to gray level
                        bmp.SetPixel(j, i, Color.FromArgb(res, res, res));
                    }
                }
                bmp.Save(Path.Combine(dst.FullName, "0-" + f.Name));

                bmp.Dispose();
                sourceImage.Dispose();
                GC.Collect();
            }
            catch { }
        }

        private static void ToBlackWhiteImageTransferHandler(FileInfo f, DirectoryInfo dst, params object[] args)
        {
            try
            {
                Image sourceImage = Image.FromFile(f.FullName);
                Bitmap bmp = new Bitmap(sourceImage);

                long avg = 0;
                for (int i = 0; i < bmp.Height; ++i)
                {
                    for (int j = 0; j < bmp.Width; ++j)
                    {
                        Color c = bmp.GetPixel(j, i);
                        int res = (int)(c.R * 0.299 + c.G * 0.587 + c.B * 0.114);  // Convert to gray level
                        bmp.SetPixel(j, i, Color.FromArgb(res, res, res));
                        avg += res;
                    }
                }
                avg /= bmp.Height * bmp.Width;

                for (int i = 0; i < bmp.Height; ++i)
                {
                    for (int j = 0; j < bmp.Width; ++j)
                    {
                        if (bmp.GetPixel(j, i).R < avg)
                        {
                            bmp.SetPixel(j, i, Color.Black);
                        }
                        else
                        {
                            bmp.SetPixel(j, i, Color.White);
                        }
                    }
                }
                bmp.Save(Path.Combine(dst.FullName, "1-" + f.Name));

                bmp.Dispose();
                sourceImage.Dispose();
                GC.Collect();
            }
            catch { }
        }

        // We use KMeansOneDimWithSortedCount here instead of KMeansOneDim because
        // there are only 256 possible gray levels, but millions pixels are possible.
        private static void ToGrayLevelTransferHandler(FileInfo f, DirectoryInfo dst, params object[] args)
        {
            int level = int.Parse(args[0].ToString());
            try
            {
                Image sourceImage = Image.FromFile(f.FullName);
                Bitmap bmp = new Bitmap(sourceImage);

                int[] count = new int[256];
                for (int i = 0; i < bmp.Height; ++i)
                {
                    for (int j = 0; j < bmp.Width; ++j)
                    {
                        Color c = bmp.GetPixel(j, i);
                        int res = (int)(c.R * 0.299 + c.G * 0.587 + c.B * 0.114);  // Convert to gray level
                        bmp.SetPixel(j, i, Color.FromArgb(res, res, res));
                        ++count[res];
                    }
                }

                double[] centers;
                {
                    double cost = 0;
                    double[] gray = new double[256];
                    for (int i = 0; i < 256; ++i) gray[i] = i;
                    centers = KMeans.KMeansOneDimWithSortedCount(gray, count, level, out cost);
                }

                // For each gray pixel find the nearest center.
                for (int i = 0; i < bmp.Height; ++i)
                {
                    for (int j = 0; j < bmp.Width; ++j)
                    {
                        int g = bmp.GetPixel(j, i).R;
                        int idx = Generic.LowerBound(centers, g);
                        if (idx == centers.Length)
                        {
                            g = (int)centers.Last();
                        }
                        else if (idx == 0)
                        {
                            g = (int)centers.First();
                        }
                        else
                        {
                            int left = (int)(g - centers[idx - 1]);
                            int right = (int)(centers[idx] - g);
                            g = (int)(left < right ? centers[idx - 1] : centers[idx]);
                        }
                        bmp.SetPixel(j, i, Color.FromArgb(g, g, g));
                    }
                }
                bmp.Save(Path.Combine(dst.FullName, level.ToString() + "-" + f.Name));

                bmp.Dispose();
                sourceImage.Dispose();
                GC.Collect();
            }
            catch { }
        }
    }
}